package Page
{
    import Regex.Regex;
    
    import TextFile.TextFile;
    
    import flash.filesystem.File;
    
    import mx.controls.Alert;
    
    [Event(name=ProgressEvent.COMPLETE, type="flash.events.ProgressEvent")]

    [Bindable]
	public class Page extends File
	{
        private static const THUMB_FOLDER_NAME:String = "thumbs";
        private static const THUMB_HEIGHT:Number = 200;
        private static const THUMB_WIDTH:Number = 125;
        private static const SPACES_STRING:String = "                                               ";
        
        private var         _index:uint;
        private var         _section:String = "";
        private var         _pagenumber:String = "";
        private var         _species:String = "";
        private var         _remark:String = "";
        private var         _textfile:TextFile;
        public var          _lines:Array;

		public function Page(index:uint, pfile:File, charset:String) {
		    _index = index;
		    super(pfile.nativePath);
            _textfile = new TextFile(textfilepath, "utf-8");
		}
		
		public function MatchCount(re:Regex):uint {
		    var str:String = this.Text;
		    if(re.unaccent) {
		        
		    }
		    return re.MatchCount(this.Text);
		}
		
		public function get MatchQuotesCount():Number {
			return MatchCount(Twister.gContext.QuotesRegex);
		}

        public function dehyphenate(): void {
            var re:RegExp = new RegExp( Twister.gContext.LETTERS_WITH_ACCENTS + "-$", "gm" );
//            var re:RegExp = new RegExp(/.*[\wä]-$/gm);
            var s:String = this.Text;
            var o:Object = re.exec(s);
            for( ; o ; o = re.exec() ) {
                var s0:String = o[0] as String;
//                var a0:Array = s0.split(/[^\wä]+/);
                var a0:Array = s0.split(Twister.gContext.WORDS_EXTENDED_REGEX);
                var w0:String = a0[a0.length-2];
//                var a1:Array = s.substr(re.lastIndex+1).match(/[\wä]+/m);
                var a1:Array = s.substr(re.lastIndex+1).match(Twister.gContext.WORDS_EXTENDED_REGEX);
                if(a1) {
                    var w1:String = a1[0];
                    var w:String = w0 + w1;
//                    trace(w);
                }
            }
        }	
        	
		public function get index():uint {
		    return this._index;
		}
		
		public function set index(i:uint):void  {
		    this._index = i;
		}
		
        private function get rows():Array {
            return Text.split("\n");    
        }

		public function get WordsArray():Array {
		    var a:Array;
		    var aret:Array = new Array();
		    var l:int;
            var s:String;
            var s0:String = "";
		    
		    // multiple hyphens to one space - not a joined word
		    for each( var r:String in this.rows ) {
                // replace (not-'-period-comma-alnum-hyphen)s with spaces
                // $line =~ s/[^'\.,\p{Alnum}-]/ /g;
                // only keep (period-'-alphanum-comma) and some high-range stuff
                // warning - guiguts de-utf8's the line first
                r = r.replace(/[^\'\.,a-z0-9\xe0-\xff-]/ig, " ");

                // replace double-hyphen with a space
                r = r.replace(/--/g, " ");
                
                // replace comma following non-digit with a space
                r = r.replace(/(\D),/g, "$1 ");
                
                // replace comma before non-digit with a space
                r = r.replace(/,(\D)/g, " $1");
                
                r = r.replace(/[,\.\']$/, "");
                r = r.replace(/[,\.\'](\s)/, "$1"); 
                        
                
                
                a = r.split(/\s+/);
                l = a.length;
                
                if( l == 0 ) {   // empty row
                    // maybe pick up hyphenated word from last row
                    if( s0.length > 0 ) {
                        aret = aret.concat( s0 );
                        s0 = "";
                    }
                    // on to the next row
                    continue;
                }

                // row has text
                // anything left at end of last row?
                if( s0.length > 0 ) {
                    // pull the first word and join them
                    s0 += ( "*" + a.shift() );
                    // add the resulting string
                    aret = aret.concat( s0 );
                    // and reset the hyphenated string
                    s0 = "";
                }
                
                // if we've depleted the arraay, loop again.
                l = a.length;
                if( l == 0 ) {
                    continue;
                }

                // hyphenated word at end?
                s0 = a[l-1];
                if( s0.substr( -1 ) != "-" ) {
                    // no? unset test string from end
                    s0 = "";
                }
                // otherwise keep it for next loop and pop it off.
                else {
                    a.pop();
                }      
                aret = aret.concat( a );
		    }
		    // if anything left hyphenated at end of page, add it
		    if( s0.length > 0 ) {
                aret = aret.concat( s0 );
		        s0 = "";
		    }
            return aret ;
	}
		
		public function get TextFileName():String {
		    return this._textfile.name ;
		}
		
		public function set TextFileName(s:String):void {
		    
		}
		
		public function get ImageFileName():String {
		    return this.name ;
		}
		
		public function set ImageFileName(s:String):void {
		    
		}
		
//		public function get imageCaption():String {
//	        return PageName;
//		}
		
        public function get section():String {
            return _section;
        }
        
        public function set section(pStr:String):void {
            _section = pStr;     
        }
        
        public function get pageNumber():String {
            return _pagenumber;
        }
        
        public function set pageNumber(pStr:String):void {
            _pagenumber = pStr.replace(" ", "");            
        }
        public function get species():String {
            return _species;
        }
        
        public function set species(pStr:String):void {
            _species = pStr;            
        }

        public function get remark():String {
            return _remark;
        }
        
        public function set remark(pStr:String):void {
            _remark = pStr;            
        }

        private function get textName():String {
        	return name.replace(extension, "txt");
        }
        
        public function get Text():String {
                return _textfile.Text;
        }
        
        private function get htmlPrefix():String {
            return  "<html>\n" + 
                    "<head>\n" + 
                    "<title>Preview</title>\n" + 
                    "<style type='text/css'>\n" + 
	                    ".text {fontSize: 2em;fontFamily: Courier}\n" +
	                    "I { color: red } \n" + 
	                    ".SC {color: blue; font-variant: small-caps; font-size: 1.3em; \n" +
	                    ".quote { color: red } \n" +
	                   	"A {text-decoration: underline} \n" +
                    "\n</style> \n" +
                    "<script type='text/javascript'>\n" +
            	        "function divtext() { return document.getElementById('text'); } \n" +
                	    "function bigger() {\n" + 
	                        "var sz = divtext().style.fontSize;\n" +
	                        "var isz = parseInt(sz);\n" +
	                        "if(isz >= 36) {\n" + 
		                        "return;\n" + 
	                        "}\n" +
	                        "isz++;\n" +
	                        "divtext().style.fontSize = isz.toString() + 'px';\n" +
	                    "}\n" + 
	                    "function setFontFamily(fname) {\n" + 
	                        "divtext().style.fontFamily = fname;\n" + 
	                    "}\n\n" +
	                    "function getFontFamily() {\n" + 
	                        "return divtext().style.fontFamily;\n" + 
	                    "}\n\n" +
	                    "function setFontSize(fsize) {\n" + 
	                        "divtext().style.fontSize = fsize;\n" + 
	                    "}\n\n" +
	                    "function smaller() {\n" + 
	                        "var sz = divtext().style.fontSize;\n" +
	                        "var isz = parseInt(sz);\n" +
	                        "if(isz >= 36) {\n" + 
	                        "return;\n" + 
	                        "}\n" +
	                        "isz--;\n" +
	                        "divtext().style.fontSize = isz.toString() + 'px';\n" +
	                    "}\n" + 
                    "</script>\n" +
                    "</head>\n" +
                    "<body>\n" + 
                    "<div id='text'>\n" + 
                    "<pre>\n";
        }
        
        private function get htmlSuffix():String {
            return  "</pre>\n" + 
                    "</div>\n" + 
                    "</body>\n" + 
                    "</html>";
        }

        public function get HtmlText():String {
            if(! isText) {
                return "<h3>No text available for this page.</h3>"
            }
            
            var stext:String = Text;
            var ptr:int = 0;
            var hstr:String = "";
            var s:String;

            stext = stext.replace(/\&/g, "&amp;");
            stext = replaceMath(stext);
            stext = stext.replace('<sc>', '<span styleName="sc">').replace('</sc>', '</span>');
//            hstr = htmlPrefix;
            hstr += stext;            
//            hstr += htmlSuffix;
//            trace(hstr);
            return hstr;
        }
        
        public function set HtmlText(s:String):void {
            
        }
        
        private function replaceMath(str:String):String {
            var re:RegExp = /<math>(.*)<\/math>/gm;
            var ret:String = "";
            
            for( ; ; ) {
                var o:Object = re.exec(str);
                if(!o) {
                    break;
                }
                ret += o[0];
            }
            return str;
        }
        
        public function set Text(str:String):void {
            if(! _textfile.exists || _textfile.Text == str) {
                return;
            }
            
            _textfile.Text = str;
//            this.dispatchEvent(new TextEvent(TextEvent.TEXT_INPUT, false, true, oldtext));
        }
        
        public function get newFileName():String {
            if(_section == "" && _pagenumber == "" && _species == "") {
                return "";
            }
            var ret:String = _section + _pagenumber;
            if(ret.length > 0 && species.length > 0)
                ret += "-";
            ret += _species;
            ret += ".";
            ret += extension;
            return ret;
        }
        
        public function set PageName(s:String):void {
        	
        }
        public function get PageName():String {
        	return ImageFileName.replace(/\.\w*$/, "");
        }
        
        public function get newTextName():String {
        	return this.newFileName.replace(extension, "txt");
        }
        
        public function setPageNumber(from:String, offset:int):void {
            var mode:String = inferMode(from);
            var i:int = 0;
            switch(mode) {
                case "i":
                    i = parseInt(from, 10) + offset;
                    pageNumber = ("00000" + i.toString()).substr(-from.length);
                    return;
                    
                case "b":
                    i = parseInt(from,10) + (offset + 1)/2 ;
                    pageNumber = ("00000" + i.toString() + (offset % 2 === 0 ? "b" : "a")).substr(-from.length);
                    return;
                   
                case "a":
                    i = parseInt(from,10);
                    i += (offset)/2;
                    pageNumber = ("00000" + i.toString() + (offset % 2 === 0 ? "a" : "b")).substr(-from.length);
                    return;
                    
                default:
                    return;
            }
        }
        
        private function get thumbFolderPath():String {
             return buildPath(folderPath, THUMB_FOLDER_NAME);
        }

        private function get thumbnailFile():File {
        	var p:String = buildPath(thumbFolderPath, ImageFileName);
            var f:File = resolvePath(p);
            if(!f || !f.exists || f.isDirectory) {
                return null;
            } else {
                return f;
            }
        }
        
        private function get thumbnailExists():Boolean {
        	return (thumbnailFile !== null && thumbnailFile.exists);
        }
        
        public function get thumbnailUrl():String {
            var s:String;
        	if(thumbnailExists) {
	            s = thumbnailFile.url;
	        } else {
        		s = "";
        	}
        	return s;
        }
        
        public function get ImageUrl():String {
            return url;
        }
        
        public function set ImageUrl(pstr:String):void {
            
        }
        
        public function set thumbnailUrl(pStr:String):void {
        }
        
        public function get thumbnailHtml():String {
            var s:String = "<img src='" + this.thumbnailUrl + "' style='height: 100%' />";
            return s;
        }

        public function set thumbnailHtml(pStr:String):void {
        }
        
        private static function isinteger(istr:String):Boolean {
            if(istr.length === 0) {
                return false;
            }
            var re:RegExp = /\D/;
            var r:int = istr.search(re);
            // if there are non-integers, return false
            return r === -1;
        }
    
        private static function istype2(istr:String):Boolean {
            var re:RegExp = /\d+[ab]/;
            var r:int = istr.search(re);
            return r !== -1;
        }
    
        private static function inferMode(str:String):String {
            if(isinteger(str)) {
                return 'i';
            }
            if(istype2(str)) {
                return str.substr(-1) === "a" ? "a" : "b";
            }
            return '';
        }
        
        private function get folderPath():String {
        	return parent.nativePath;
        }
        
        public function get path():String {
            return nativePath.toString();    
        }
        

        public static function buildPath(p1:String, p2:String):String {
        	var ret:String = p1 + '/' + p2;
        	ret = ret.replace(/[\/\\]+/g, "/");
        	return ret;
//        	for( ; p1.length > 0 && ( p1.substr(-1) === "\\" || p1.substr(-1) === "/"); ) {
//        		p1 = p1.substr(0, p1.length-1);
//        	}
//        	for( ; p2.length > 0 && ( p2.substr(0, 1) === "\\" || p2.substr(0, 1) === "/"); ) {
//        		p2 = p2.substr(1);
//        	}
//        	return p1 + "/" + p2;
        }
        
        private function zipPath():String {
            var f:File = new File(folderPath);
            var s:String = buildPath(folderPath, f.name);
            f = f.resolvePath(s);
            if(! f.exists) {
            	f.createDirectory();
            }
        	s = buildPath(s, f.name + "-page-images");
        	f = f.resolvePath(s);
            if(! f.exists) {
                f.createDirectory();
            }
        	
        	return s;
        }
        
        public function clear():void {
            remark = "";
            section = "";
            pageNumber = "";
            species = "";	
        }
        
        private function get textfilepath():String {
            return buildPath(folderPath, textName);
        }
        
        public function get isText():Boolean {
            return _textfile.exists;
        }
        
        public function get isTextY():String {
        	return isText ? "Yes": "";
        }
        
        public function ReplaceAll(re:Regex):void {
            if(this.MatchCount(re)) {
                var s:String = re.ReplaceString(this.Text);
                this.Text = s;
            }
        }

        public function copyExport():void {
        	if(newFileName === "") {
        		return;
        	}
        	// copy image file
            var topath:String = buildPath(zipPath(), newFileName);
            var fto:File = new File(topath);
        	copyToAsync(fto, true);
        	// copy text file
             var totextpath:String = buildPath(zipPath(), newTextName);
            this._textfile.saveLatin1ToPath(totextpath);
        }
        
//        private function GetLineIndexContainingIndex( i:int ):int {
//        	var l:PageLine = getLineContainingIndex( i );   
//        	if(! l) {
//        		trace("GetLineIndexContainingIndex", i, "is null");
//        		return 0;
//        	}
//        	else {
//        		return l.PageIndex;
//        	}
//        }
//        
        public function getLineContainingIndex( i:int ):PageLine {
        	var ret:PageLine;
        	var a:Array = PageLines;
            for each( var pl:PageLine in a ) {
                if( pl.StartIndex + pl.length > i ) {
                    return pl;
                }
            }
            return PageLines[0];
        }

        private function get PageLines():Array {
            if( ! _lines ) {
                _lines = new Array();
                var o:Object;
                var iptr:int = 0;
                var l:PageLine;
                var txt:String = Text;
                var a:Array = txt.split(/\r/);
                for each(var s:String in a) {
//                    trace(s);
                    l = new PageLine(_lines.length, iptr, s.length);
//                    trace(o.line_index, o[0] );
                    _lines.push( l );
                    iptr = iptr + s.length + 1;
                }
            }
            return _lines;
        }
        
        private function PageLineText(i:int):String {
        	var pl:PageLine = getPageLine(i);
        	return Text.substr(pl.StartIndex, pl.length);
        }
        
        private function GetChunkContext( i1:int, i2:int ):String {
            var ret:String = "";
            var imin:int = Math.max( i1-i2, 0 );
            var imax:int = Math.min( i1+i2, _lines.length - 1 );
            for( var i:int = imin; i <= imax; i++) {
            	if( ret.length ) ret += "\r";
        		ret += PageLineText(i);
            }
            var s2:String = "";
            return ret;   
        }
        

        public function getPageLine( iseq:int ):PageLine {
            return PageLines[iseq];
        }
        
        private function get maxLineIndex():int {
            return PageLines.length - 1;
        }
        public function getTextInstances( regex:RegExp ):Array {
            var a:Array = new Array();
            var o_re:Object;
            var pline:PageLine;
            
            for( ; ; ) {
                o_re = regex.exec( this.Text );
                if(! o_re ) {
                    break;
                }
                var tc:PageChunk = new PageChunk( o_re[0], this.PageName, o_re.index );
                var pl:PageLine = this.getLineContainingIndex(o_re.index);
                tc.LineNumber = pl.PageIndex;
                tc.PageIndex = this.index;
				tc.LineIndex = o_re.index - pl.StartIndex;
                var imin:int = Math.max(tc.LineNumber - 2, 0);
                var imax:int = Math.min(tc.LineNumber + 2, _lines.length-1);
                var ptr1:int = _lines[imin].StartIndex;
                var ptr2:int = _lines[imax].StartIndex + _lines[imax].length;
                var s2:String = this.Text.substring(ptr1, ptr2);
                tc.ChunkContext = s2;
                tc.ContextIndex = ptr1;
				
                a.push( tc );
//                trace(tc.Text, tc.PageName, tc.PageIndex, tc.LineNumber, tc.LineIndex);
            }
            return a;
        }
        
        public function get WrappedText():String {
			return wrapped_text(72);
        }        

        private function wrapped_text(maxlen:int):String {
        	var result:String = "";
        	var accum:String = "";
        	var txt:String = Text;
        	var lines:Array = this.PageLines;
        	var iswrap:Boolean = true;
        	var indentstr:String = "";
        	 
        	
        	for each(var l:PageLine in lines) {
				var s:String = l.getText(txt);
				// if a blank line
				if(s.search(/^\s*$/) >= 0) {
					if(accum.length) {
						result += indentstr;
						result += accum;
						accum = "";
						result += "\n";
					}
					result += "\n";
					continue;
				}
				// if   /*
				if(s.search(/^\/\*\s*/) >= 0) {
        			iswrap = false;
        			continue;
        		}
        		// if not in a wrapping region
        		if( ! iswrap ) {
        			// if this is end-no-wrap ( */ )
        			if(s.search(/^\*\/\s*/) >= 0) {
        				iswrap = true;
        				continue;
        			}
        			// append the whole line
        			result += s;
        			continue;
        		}

				// we're in a wrapping region
        		// begin indent?
        		if(s.search(/^\/\#\s*/) >= 0) {
        			if(accum.length) {
        				result += indentstr;
						result += accum;
						result += "\n";
						accum = "";
        			}
        			indentstr += "  ";
        			continue;
        		}
        		// end indent?
        		if(s.search(/^\#\/\s*/) >= 0) {
        			if(indentstr.length) {
	        			if(accum.length) {
        				result += indentstr;
							result += accum;
							result += "\n";
							accum = "";
	        			}
	        			indentstr = indentstr.substr(2);
	        		}
        			continue;
        		}
        		
        		// we have a line to deal with
        		// split out the words.
        		var wds:Array = s.split(/\s+/);
        		// for each word
        		for each(var w:String in wds) {
        			// if the single word is longer than the limit
        			if(w.length >= maxlen + indentstr.length) {
        				result += indentstr;
        				result += accum;
        				result += "\n";
        				accum = w;
        				continue;
        			}
        			// if accumulater would exceeed limit
        			if(accum.length + w.length + indentstr.length > maxlen) {
        				result += indentstr;
        				result += accum;
        				result += "\n";
        				accum = w;
        				continue;
        			}
        			// if something already in accumulator, add a space.
        			if(accum.length) {
	        			accum += " ";
	        		}
        			accum += w;
        		}
        	}
			result += indentstr;
        	result += accum;
        	return result;
        }
    }
}
